# 一定要先装好轮子哦
#                   -- 原作者：mihu
#                   -- 原项目：https://github.com/3150601355/ZiDongGuoJuQing

import ctypes  # python自带 不用安装
import os
import random  # python自带 不用安装
import sys
import threading
import time
from ctypes import wintypes
from datetime import datetime

import cv2  # pip install opencv-python
import numpy  # pip install numpy
import psutil  # pip install psutil
from PIL import ImageGrab  # pip install pillow
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot  # pip install PyQt5
from PyQt5.QtGui import QPainter, QColor, QBrush
from PyQt5.QtWidgets import QApplication, QWidget
from colorama import init, Fore, Style  # pip install colorama


class POINT(ctypes.Structure):
    _fields_ = [("x", wintypes.LONG),
                ("y", wintypes.LONG)]


class PlotTool():
    def __init__(self):
        self.user32 = ctypes.windll.user32
        self.screen_width = 1920
        self.screen_height = 1080
        self.pre_game_name = None
        self.play_button_img_templ = None  # 剧情播放按钮模板图片
        self.plot_button_img_templ = None  # 剧情对话选项模板图片
        self.THRESHOLD = 0.95
        self.is_games_in_front = False
        self.status = 0  # 状态（0：未启用-红色 1:启用-绿色 2:启用且在剧情中但不需要点击-黄色 3：启用但未在剧情中-绿色 4：启用且在剧情中-深绿色）
        self.pre_log = None
        self.point = POINT()

        self.screen_width, self.screen_height = get_screen_resolution()  # 获取屏幕分辨率

    def check(self):
        # 检测前台程序
        game_name_list = ['GenshinImpact.exe', 'YuanShen.exe', 'StarRail.exe']
        game_name = get_foreground_process_name()
        if game_name not in game_name_list:
            self.log(f'{Fore.YELLOW}当前为{Fore.WHITE}{game_name}{Fore.YELLOW}程序界面，不是游戏界面{Style.RESET_ALL}')
            self.is_games_in_front = False
            return False
        self.is_games_in_front = True
        if game_name == 'GenshinImpact.exe' or game_name == 'YuanShen.exe':
            if self.pre_game_name != game_name:
                py_path = os.path.abspath(__file__)
                # 获取当前脚本文件所在的目录
                assets_path = os.path.dirname(py_path)
                self.pre_game_name = game_name
                self.load_img_src(f'{assets_path}\\assets\\play_button.jpg', f'{assets_path}\\assets\\plot_button.jpg')
                self.status = 0
        else:
            if self.pre_game_name != game_name:
                py_path = os.path.abspath(__file__)
                # 获取当前脚本文件所在的目录
                assets_path = os.path.dirname(py_path)
                self.pre_game_name = game_name
                self.load_img_src(f'{assets_path}\\assets\\sr_play_button.jpg')
                self.status = 0

        x, y = self.get_cursor_pos()
        if y > self.screen_height / 5 and x < self.screen_width / 10:
            if self.status != 0:
                self.status = 0
                self.log(f'{Fore.RED}关闭剧情跳过功能{Style.RESET_ALL}')
                return False
        elif y > self.screen_height / 5 and x > self.screen_width / 10 * 9 and self.status == 0:
            self.status = 1
            self.log(f'{Fore.GREEN}启动剧情跳过功能{Style.RESET_ALL}')

        if self.status == 0:
            self.log(f'{Fore.MAGENTA}剧情跳过功能未开启{Style.RESET_ALL}')
            return False
        return True

    def run(self):
        if not self.check():
            return False
        if self.cursor_is_move() is True:
            return False

        if self.pre_game_name == 'GenshinImpact.exe' or self.pre_game_name == 'YuanShen.exe':
            # 匹配播放按钮
            play_button_suitability = PlotTool.mainLoop(59, 32, 83, 64, self.play_button_img_templ)
            self.log(f'播放按钮匹配度:{play_button_suitability:>6.3f}', 'delay')

            if play_button_suitability > self.THRESHOLD:
                if self.point.y > self.screen_height / 5:
                    self.cursor_click(1370, 800)
                    self.status = 4
                    return True
                else:
                    self.status = 2
            elif play_button_suitability > 0.2:
                plot_button_suitability = PlotTool.mainLoop(1280, 788, 1317, 820, self.plot_button_img_templ)
                self.log(f'剧情按钮匹配度:{plot_button_suitability:>6.3f}', 'not')
                if plot_button_suitability > self.THRESHOLD:
                    if self.point.y > self.screen_height / 5:
                        self.cursor_click(1370, 800)
                        self.status = 4
                        return True
                    else:
                        self.status = 2
                else:
                    self.status = 3
            else:
                self.status = 3
        elif self.pre_game_name == 'StarRail.exe':
            # 匹配播放按钮
            play_button_suitability = PlotTool.mainLoop(145, 50, 157, 70, self.play_button_img_templ)
            self.log(f'播放按钮匹配度:{play_button_suitability:>6.3f}', 'delay')

            if play_button_suitability > self.THRESHOLD:
                if self.point.y > self.screen_height / 5:
                    self.cursor_click(1390, 760)
                    self.status = 4
                    return True
                else:
                    self.status = 2
            else:
                self.status = 3
        return False

    def load_img_src(self, play_button_path, plot_button_path=None):
        self.play_button_img_templ = cv2.imread(play_button_path)
        if plot_button_path is not None:
            self.plot_button_img_templ = cv2.imread(plot_button_path)
        self.log(f'{Fore.BLUE}加载{self.pre_game_name}资源{Style.RESET_ALL}')

    @staticmethod
    def mainLoop(x1, y1, x2, y2, img_templ):
        img_src = ImageGrab.grab(bbox=(x1, y1, x2, y2))  # x1, y1, x2, y2
        img_src = cv2.cvtColor(numpy.asarray(img_src), cv2.COLOR_RGB2BGR)
        # 模板匹配
        result = cv2.matchTemplate(img_src, img_templ, cv2.TM_CCOEFF_NORMED)
        result_min_max = cv2.minMaxLoc(result)  # 计算匹配度
        return result_min_max[1]

    def get_cursor_pos(self):
        self.user32.GetCursorPos(ctypes.byref(self.point))
        return self.point.x, self.point.y

    def cursor_is_move(self):
        if self.status == 1 or self.status == 3:
            return False
        pre_x, pre_y = self.get_cursor_pos()
        time.sleep(0.01)
        self.get_cursor_pos()
        if pre_x == self.point.x and pre_y == self.point.y:
            return False
        self.log(f'{Fore.YELLOW}用户控制鼠标中{Style.RESET_ALL}', 'together')
        self.status = 2
        return True

    def cursor_click(self, x=None, y=None):
        cursor_x, cursor_y = self.get_cursor_pos()
        if (x is not None and y is not None) and (cursor_x != x or cursor_y != y):
            self.user32.SetCursorPos(x, y)
            self.log(f'{Fore.CYAN}设置光标位置{Style.RESET_ALL}', 'not')
        ctypes.windll.user32.mouse_event(2)
        time.sleep(0.05 + 0.1 * random.random())
        ctypes.windll.user32.mouse_event(4)
        self.log(f'{Fore.GREEN}模拟鼠标单击{Style.RESET_ALL}', 'together')

    def log(self, log_str, is_output=None):
        now_time = datetime.now()
        if is_output is None:  # 立即输出
            if self.pre_log is None:
                print(
                    f'[{now_time.year}-{now_time.month}-{now_time.day} {now_time.hour:}:{now_time.minute:>02}:{now_time.second:>02} {int(now_time.microsecond / 1000):>3}] {log_str}')
            else:
                print(self.pre_log)
                self.pre_log = None
                print(
                    f'[{now_time.year}-{now_time.month}-{now_time.day} {now_time.hour:}:{now_time.minute:>02}:{now_time.second:>02} {int(now_time.microsecond / 1000):>3}] {log_str}')
        elif is_output == 'delay':  # 输出旧的，不输出新的
            if self.pre_log is None:
                self.pre_log = f'[{now_time.year}-{now_time.month}-{now_time.day} {now_time.hour:}:{now_time.minute:>02}:{now_time.second:>02} {int(now_time.microsecond / 1000):>3}] {log_str}'
            else:
                print(self.pre_log)
                self.pre_log = f'[{now_time.year}-{now_time.month}-{now_time.day} {now_time.hour:}:{now_time.minute:>02}:{now_time.second:>02} {int(now_time.microsecond / 1000):>3}] {log_str}'
        elif is_output == 'not':  # 都不输出
            if self.pre_log is None:
                self.pre_log = f'[{now_time.year}-{now_time.month}-{now_time.day} {now_time.hour:}:{now_time.minute:>02}:{now_time.second:>02} {int(now_time.microsecond / 1000):>3}] {log_str}'
            else:
                self.pre_log = f'{self.pre_log}，{log_str}'
        elif is_output == 'together':  # 新旧一起输出
            if self.pre_log is None:
                print(
                    f'[{now_time.year}-{now_time.month}-{now_time.day} {now_time.hour:}:{now_time.minute:>02}:{now_time.second:>02} {int(now_time.microsecond / 1000):>3}] {log_str}')
            else:
                print(f'{self.pre_log}，{log_str}')
                self.pre_log = None


class CircleWindow(QWidget):
    # 自定义信号，传递颜色和窗口显示状态
    colorChanged = pyqtSignal(QColor)
    visibilityChanged = pyqtSignal(bool)

    def __init__(self):
        super().__init__()

        self.screen_width, self.screen_height = get_screen_resolution()  # 获取屏幕分辨率
        self.circle_color = Qt.red  # 初始化圆的颜色为红色
        self.setGeometry(int(self.screen_width / 2 - 10), int(self.screen_height - 30), 20, 20)  # 设置窗口大小
        self.setWindowFlags(
            Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool | Qt.WindowTransparentForInput)  # 窗口顶置、无边框、工具窗口、完全忽略鼠标事件(穿透点击效果)
        self.setAttribute(Qt.WA_TranslucentBackground)  # 设置窗口透明

        # 连接信号和槽
        self.colorChanged.connect(self.changeColor)
        self.visibilityChanged.connect(self.changeVisibility)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)  # 抗锯齿

        # 绘制实心圆
        painter.setBrush(QBrush(self.circle_color, Qt.SolidPattern))
        painter.setOpacity(1)  # 设置完全不透明
        painter.drawEllipse(0, 0, 20, 20)  # 以（500，500）为圆心，半径为100的圆

    @pyqtSlot(QColor)
    def changeColor(self, color):
        self.circle_color = color
        self.update()  # 重新绘制

    @pyqtSlot(bool)
    def changeVisibility(self, visible):
        if visible:
            self.show()
        else:
            self.hide()


def get_screen_resolution():
    screen_width = ctypes.windll.user32.GetSystemMetrics(0)
    screen_height = ctypes.windll.user32.GetSystemMetrics(1)
    return screen_width, screen_height


def get_foreground_process_name():
    # 获取前台窗口句柄
    hwnd = ctypes.windll.user32.GetForegroundWindow()
    # 获取窗口所属进程ID
    pid = ctypes.c_ulong()
    ctypes.windll.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
    # 使用psutil获取进程信息
    process = psutil.Process(pid.value)
    return process.name()


def run_plot_tool(window):
    Qt_color_list = [Qt.red, Qt.green, Qt.yellow, Qt.green, Qt.cyan]
    pre_tool_status = 0
    pre_is_games_in_front = False
    plottool = PlotTool()
    while True:
        try:
            res = plottool.run()
            if pre_tool_status != plottool.status:
                pre_tool_status = plottool.status
                window.colorChanged.emit(Qt_color_list[pre_tool_status])
            if pre_is_games_in_front != plottool.is_games_in_front:
                pre_is_games_in_front = plottool.is_games_in_front
                window.visibilityChanged.emit(pre_is_games_in_front)

            if res is True:
                time.sleep(random.uniform(0.18, 0.24))
            else:
                time.sleep(1)
        except KeyboardInterrupt:
            plottool.log(f'{Fore.RED}程序退出{Style.RESET_ALL}')
            exit(0)


if __name__ == '__main__':
    # 初始化 colorama
    init(autoreset=True)
    # 判断当前进程是否以管理员权限运行
    if not ctypes.windll.shell32.IsUserAnAdmin():
        print('当前不是以管理员权限运行，即将重启以管理员权限启动新进程...')
        ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
        exit(0)
    app = QApplication(sys.argv)
    window = CircleWindow()

    # 创建并启动子线程
    thread = threading.Thread(target=run_plot_tool, daemon=True, args=(window,))
    thread.start()

    sys.exit(app.exec_())
